using System;
using System.Web;
using System.Web.Caching;
using System.Collections.Generic;
using System.Threading;

namespace SBPweb.Forge.Caching
{
	/// <summary>
	/// Class of CacheHandler
	/// </summary>
    public static class CacheHandler
	{
	
		#region Private members

        private static HttpRuntime httpRuntime;

		private static ReaderWriterLock rwLock = new ReaderWriterLock();
		private static bool isCloning;
		private static int readerLockTimeout = 1000;
		private static int writerLockTimeout = 1000;

		private static Dictionary<string, CacheAccessInfo> accessLog = new Dictionary<string, CacheAccessInfo>();

		private static Cache cache
        {
            get
            {
                EnsureHttpRuntime();
				return HttpRuntime.Cache;
            }
		}

        private static void EnsureHttpRuntime()
        {
            if (null == httpRuntime)
            {
                try
                {
                    Monitor.Enter(typeof(CacheHandler));
                    if (null == httpRuntime)
                    {
                        httpRuntime = new HttpRuntime();
                    }
                }
                finally
                {
                    Monitor.Exit(typeof(CacheHandler));
                }
            }
        }

		#endregion

		#region Constructor logic

		#endregion

		#region Public properties

		/// <summary>
		/// Is in cloning state?
		/// </summary>
		public static bool IsCloning
		{
			get
			{
				return isCloning;
			}
			set
			{
				isCloning = value;
			}
		}

		#endregion

		#region Generic cache handling

		/// <summary>
		/// Get CacheAccessInfo clone
		/// </summary>
		/// <returns>Dictionary of CacheAccessInfo</returns>
		public static Dictionary<string, CacheAccessInfo> GetAccessLogClone()
		{
			rwLock.AcquireReaderLock(readerLockTimeout);
			try
			{
				Dictionary<string, CacheAccessInfo> ret = new Dictionary<string, CacheAccessInfo>();
				foreach (string key in accessLog.Keys)
				{
					ret.Add(key, accessLog[key]);
				}
				return ret;
			}
			finally
			{
				rwLock.ReleaseReaderLock();
			}
		}

		/// <summary>
		/// Contains key
		/// </summary>
		/// <param name="key">key</param>
		/// <returns>true or false</returns>
		public static bool ContainsKey(string key)
		{
			rwLock.AcquireReaderLock(readerLockTimeout);
			try
			{
				return (accessLog.ContainsKey(key));
			}
			finally
			{
				rwLock.ReleaseReaderLock();
			}
		}

		/// <summary>
		/// Retrieve
		/// </summary>
		/// <typeparam name="T">T</typeparam>
		/// <param name="key">key</param>
		/// <returns>object</returns>
		public static T Retrieve<T>(string key)
		{
			rwLock.AcquireReaderLock(readerLockTimeout);
			try
			{
				if (accessLog.ContainsKey(key))
                {
                    CacheAccessInfo cap = accessLog[key];
                    cap.LastRetrieveAt = DateTime.Now;
                }
                return (T)cache.Get(key);
			}
			finally
			{
				rwLock.ReleaseReaderLock();
			}
        }

		/// <summary>
		/// Store
		/// </summary>
		/// <typeparam name="T">T</typeparam>
		/// <param name="key">key</param>
		/// <param name="value">value</param>
		/// <param name="fileName">file</param>
		/// <returns>stored object</returns>
		public static T Store<T>(string key, T value, string fileName)
		{
			return Store<T>(key, value, new CacheDependency(fileName), DateTime.MaxValue, TimeSpan.Zero, CacheItemPriority.Default, null);
		}

		/// <summary>
		/// Store
		/// </summary>
		/// <typeparam name="T">T</typeparam>
		/// <param name="key">key</param>
		/// <param name="value">value</param>
		/// <param name="slidingExpiration">sliding expiration</param>
		/// <returns>stored object</returns>
		public static T Store<T>(string key, T value, TimeSpan slidingExpiration)
		{
			return Store<T>(key, value, null, DateTime.MaxValue, slidingExpiration, CacheItemPriority.Default, null);
		}

		/// <summary>
		/// Store
		/// </summary>
		/// <typeparam name="T">T</typeparam>
		/// <param name="key">key</param>
		/// <param name="value">value</param>
		/// <param name="slidingExpiration">sliding expiration</param>
		/// <param name="priority">cache priority</param>
		/// <returns>stored object</returns>
		public static T Store<T>(string key, T value, TimeSpan slidingExpiration, CacheItemPriority priority)
		{
			return Store<T>(key, value, null, DateTime.MaxValue, slidingExpiration, priority, null);
		}

		/// <summary>
		/// Store
		/// </summary>
		/// <typeparam name="T">T</typeparam>
		/// <param name="key">key</param>
		/// <param name="value">value</param>
		/// <param name="dependencies">cache dependencies</param>
		/// <param name="absoluteExpiration">absolute expiration</param>
		/// <param name="slidingExpiration">sliding expiration</param>
		/// <param name="priority">cache priority</param>
		/// <param name="onRemoveCallback">callback of remove</param>
		/// <returns>stored object</returns>
		public static T Store<T>(string key, T value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback)
		{
			rwLock.AcquireWriterLock(writerLockTimeout);
			try
			{
				if (accessLog.ContainsKey(key))
				{
					accessLog.Remove(key);
				}
				if (absoluteExpiration != null && absoluteExpiration != DateTime.MaxValue)
				{
					accessLog.Add(key, new CacheAccessInfo(DateTime.Now, DateTime.Now, absoluteExpiration, priority));
				}
				else if (slidingExpiration != null && slidingExpiration != TimeSpan.Zero && slidingExpiration != TimeSpan.MinValue)
				{
					accessLog.Add(key, new CacheAccessInfo(DateTime.Now, DateTime.Now, slidingExpiration, priority));
				}
				else
				{
					accessLog.Add(key, new CacheAccessInfo(DateTime.Now, DateTime.Now, priority));
				}
				if (onRemoveCallback == null)
				{
					onRemoveCallback = new CacheItemRemovedCallback(OnRemoveCallback);
				}
				else
				{
					onRemoveCallback += new CacheItemRemovedCallback(OnRemoveCallback);
				}
				object o = cache.Add(key, value, dependencies, absoluteExpiration, slidingExpiration, priority, onRemoveCallback);
				if (o == null)
				{
					return default(T);
				}
				return (T)o;
			}
			finally
			{
				rwLock.ReleaseWriterLock();
			}
		}

		/// <summary>
		/// Remove
		/// </summary>
		/// <typeparam name="T">T</typeparam>
		/// <param name="key">key</param>
		/// <returns>removed item</returns>
		public static T Remove<T>(string key)
		{
			rwLock.AcquireWriterLock(writerLockTimeout);
			try
			{
				return (T)cache.Remove(key);
			}
			finally
			{
				rwLock.ReleaseWriterLock();
			}
		}

		private static void OnRemoveCallback(string key, object value, CacheItemRemovedReason reason)
		{
			try
			{
				rwLock.AcquireWriterLock(writerLockTimeout);
				try
				{
					if (accessLog.ContainsKey(key))
					{
						accessLog.Remove(key);
					}
				}
				catch (ArgumentNullException)
				{
				}
				finally
				{
					rwLock.ReleaseWriterLock();
				}
			}
			catch (ArgumentNullException)
			{
			}
			catch (ArgumentOutOfRangeException)
			{
			}
			catch (ApplicationException)
			{
			}
		}

		/// <summary>
		/// Retrieve entity
		/// </summary>
		/// <typeparam name="T">T</typeparam>
		/// <param name="key">key</param>
		/// <returns>entity</returns>
		public static T RetrieveEntity<T>(string key) where T : ICloneable
		{
			T ret = Retrieve<T>(key);
			if (IsCloning)
			{
				return (T)ret.Clone();
			}
			return ret;
		}

		/// <summary>
		/// Retrieve list
		/// </summary>
		/// <typeparam name="T">T</typeparam>
		/// <param name="key">key</param>
		/// <returns>list</returns>
		public static List<T> RetrieveList<T>(string key) where T : ICloneable
		{
			List<T> list = Retrieve<List<T>>(key);
			if (IsCloning)
			{
				List<T> retlist = new List<T>();
				foreach (T item in list)
				{
					retlist.Add((T)item.Clone());
				}
				return retlist;
			}
			return list;
		}

		/// <summary>
		/// Retrieve dictionary
		/// </summary>
		/// <typeparam name="K">K</typeparam>
		/// <typeparam name="T">T</typeparam>
		/// <param name="key">key</param>
		/// <returns>dictionary</returns>
		public static Dictionary<K, T> RetrieveDictionary<K, T>(string key) where T : ICloneable
		{
			Dictionary<K, T> list = Retrieve<Dictionary<K, T>>(key);
			if (IsCloning)
			{
				Dictionary<K, T> retlist = new Dictionary<K, T>();
				foreach (K listkey in list.Keys)
				{
					retlist.Add(listkey, list[listkey]);
				}
				return retlist;
			}
			return list;
		}

		/// <summary>
		/// Retrieve list of dictionary
		/// </summary>
		/// <typeparam name="K">K</typeparam>
		/// <typeparam name="T">T</typeparam>
		/// <param name="key">key</param>
		/// <returns>List</returns>
		public static Dictionary<K, List<T>> RetrieveListDictionary<K, T>(string key) where T : ICloneable
		{
			Dictionary<K, List<T>> list = Retrieve<Dictionary<K, List<T>>>(key);
			if (IsCloning)
			{
				Dictionary<K, List<T>> retlist = new Dictionary<K, List<T>>();
				foreach (K listkey in list.Keys)
				{
					retlist.Add(listkey, new List<T>());
					if (list[listkey] != null)
					{
						foreach (T item in list[listkey])
						{
							retlist[listkey].Add((T)item.Clone());
						}
					}
				}
				return retlist;
			}
			return list;
		}

		#endregion

		#region Mass destruction 

		/// <summary>
		/// Clear everything
		/// </summary>
		public static void Clear()
		{
			RemoveByKeySubstring(null);
		}

		/// <summary>
		/// Remove by key substring
		/// </summary>
		/// <param name="keySubstring">substring</param>
		public static void RemoveByKeySubstring(string keySubstring)
		{
			List<string> killList = new List<string>();
			rwLock.AcquireReaderLock(readerLockTimeout);
			try
			{
				foreach (string key in accessLog.Keys)
				{
					if (keySubstring == null || key.StartsWith(keySubstring, StringComparison.CurrentCulture))
					{
						killList.Add(key);
					}
				}
			}
			finally
			{
				rwLock.ReleaseReaderLock();
			}
			foreach (string key in killList)
			{
				cache.Remove(key);
			}
		}

		#endregion

	}
}